﻿using System;
using System.Threading;

using Microsoft.SPOT;

using mfLogger;

using OST.Shared;
using OST.Shared.Extensions;
using OST.Shared.Interfaces;
using OST.Shared.SharedTypes;
using OST.Anemometer;


namespace OST.Calc.Tracking
{
    using Extensions;
    using OST.Persistence;
    using OST.Calc.Frame;

    /// <summary>
    /// implementation for <see cref="ITracker"/>
    /// </summary>
    public class Tracker : ITracker
    {
        #region Fields
        private IMotor _motor = null;
        private bool _stopped = false;
        private Thread _thRunner = null;
        #endregion

        #region CTOR

        public Tracker(IMotor motor, FrameConfiguration frameConfig, LocationConfiguration locationConfig)
        {
            if (motor == null || motor != null && !motor.IsInitialized)
                throw new OSTException("no motor instance available or not initialized for tracker");

            _motor = motor;

            FrameConfig = frameConfig;
            LocationConfig = locationConfig;

            IsWindAlarmActive = false;
        }
        
        #endregion

        #region Properties
        /// <summary>
        /// wrapper-property 4 logging
        /// </summary>
        private ILoggerAppender Logger
        {
            get
            {
                return mfLogger.mfLogger.Instance;
            }
        }

        private FrameConfiguration FrameConfig { get; set; }
        private LocationConfiguration LocationConfig { get; set; }

        private TrackingAction CurrentAction { get; set; }
        #endregion

        #region Methods
        private void RunnerAsync()
        {
            Day = null;
            int currentActionIndex = 0;
            int nextActionIndex = 0;
            bool wasWindAlarmActive = false;

            //check wind-speed, if anemometer-provider is availabe
            if (!AnemometerProvider.Instance.HasInstance)
                Logger.Warning("Tracker: no anemometer-provider found!");

            do
            {
                //check wind-speed, if anemometer-provider is availabe
                if (CheckWindAlarm())
                {
                    Drive2Center();
                    wasWindAlarmActive = true;
                    continue;
                }
                else if (wasWindAlarmActive) //after wind-alarm is over...
                {
                    wasWindAlarmActive = false;
                    //...re-execute the current-action
                    ExecuteDailyAction(CurrentAction);
                }

                //check if day changed
                if (Day == null                                             //for first entry
                    || (Day != null && Day.Day.Day != DateTime.Now.Day))    //&& for day-change-(re)calculation
                {
                    Logger.Debug("Tracker: day-changed detected...");

                    //re-calculate daily-data
                    Day = TrackingCalculator.CalculateDay(DateTime.Now, FrameConfig, LocationConfig);

                    currentActionIndex = Day.Try2FindInitialActionIndex();
                    nextActionIndex = Day.NextActionIndex(currentActionIndex);
                    CurrentAction = Day.GetAction(currentActionIndex);
                    NextAction = Day.GetAction(nextActionIndex);

                    ExecuteDailyAction(Day.Action[currentActionIndex]);

                    Logger.Debug("Tracker: " + Debug.GC(true) + " Bytes are available...");
                    continue;//next loop
                }

                //check, if current action-index is overdriven
                if (NextAction.Sunposition.Timestamp < DateTime.Now
                    && CurrentAction.Sunposition.Timestamp != NextAction.Sunposition.Timestamp)
                {
                    Logger.Debug("Tracker: try to find an execute next-action executed");

                    currentActionIndex = nextActionIndex;
                    CurrentAction = NextAction;
                    nextActionIndex = Day.NextActionIndex(nextActionIndex);
                    NextAction = Day.GetAction(nextActionIndex);

                    ExecuteDailyAction(CurrentAction);

                    Logger.Info("Tracker.Current=" + CurrentAction);
                    Logger.Info("Tracker.Next=" + NextAction);
                }

                Thread.Sleep(10000);//suspend 4 10 seconds
            } while (!_stopped);

            Logger.Debug("Tracker.RunnerAsync() stopped!");
        }

        private void Drive2Center()
        {
            var frameConfig = ConfigurationProvider.Instance.ConfigInstance.Frame;

            //calc motor-zero
            var center = FrameCalculator.CalcMotorStrokeZero(frameConfig.Arm
                                                                , frameConfig.Leg
                                                                , frameConfig.Body);

            _motor.Move2MotorPositionAsync(center);
        }

        private bool CheckWindAlarm()
        {
            if (AnemometerProvider.Instance.HasInstance && ConfigurationProvider.Instance.HasConfig)
            {
                var anemometer = AnemometerProvider.Instance.AnemometerInstance;
                var frameconfig = ConfigurationProvider.Instance.ConfigInstance.Frame;

                if (anemometer.WindSpeedAverage >= frameconfig.WindAlarm)
                {
                    Logger.Warning("Tracker: WIND-ALARM! " + anemometer.WindSpeedAverage 
                        + " of allowed " + frameconfig.WindAlarm + "...driving to center");
                    IsWindAlarmActive = true;
                    return IsWindAlarmActive;
                }
            }
            IsWindAlarmActive = false;
            return IsWindAlarmActive;
        }

        private void ExecuteDailyAction(TrackingAction action)
        {
            _motor.Move2MotorPositionAsync(action.MotorTarget);
            Logger.Debug("Tracker.ExecuteDailyAction(action=" + action + ") executed");
        }

        #endregion

        #region ITracker

        public void Start()
        {
            if (_motor == null)
            {
                Logger.Error("Tracker.Start(): not initialized");
                return;
            }
            if (_thRunner != null)
            {
                Logger.Warning("Tracker.Start(): already executed");
                return;
            }

            Logger.Debug("Tracker.Start() executing");

            _stopped = false;

            _thRunner = new Thread(new ThreadStart(RunnerAsync));
            _thRunner.Start();

            IsWindAlarmActive = false;

            OnNotifyPropertyChanged("IsStarted", true);
            Logger.Info("Tracker started");
        }

        public void Stop()
        {
            Logger.Debug("Tracker.Stop() started");

            _stopped = true;
            _thRunner.Abort();
            _thRunner = null;

            Day = null;
            CurrentAction = null;
            NextAction = null;

            IsWindAlarmActive = false;

            OnNotifyPropertyChanged("IsStarted", false);
            Logger.Info("Tracker stopped");
        }

        public bool IsStarted
        {
            get { return _thRunner != null; }
        }

        public TrackingDay Day
        {
            get;
            private set;
        }

        public TrackingAction NextAction { get; private set; }

        public bool IsWindAlarmActive { get; private set; }
        #endregion


        #region INotifyPropertyChanged

        /// <summary>
        /// notifies clients over changes in puplic properties
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnNotifyPropertyChanged(String propertyName, object newValue)
        {
            if (PropertyChanged != null && !propertyName.IsNullOrEmpty())
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName, newValue));
            }
        }

        #endregion

    }
}
